﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using System.Xml.Linq;
using System.Linq;
using Microsoft.Data.Entity.Design.Extensibility;
using System.Globalization;
using System.ComponentModel.Design;
using Microsoft.Win32;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;

namespace Liekhus.EntityFramework.XAFExtensions.Generator
{
    [Export(typeof(IModelTransformExtension))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class XAFGenerator : IModelTransformExtension
    {
        #region Modular Variables
        private IDictionary<string, XElement> m_entities = null;
        private IDictionary<string, XElement> m_associations = null;
        private IDictionary<string, XElement> m_mappings = null;
        private IDictionary<string, string> m_links = null;
        private List<string> mlsProjectRefs = null;
        private string msGenCodePath = string.Empty;
        private string msDevXVersion = string.Empty;
        private EnvDTE.ProjectItem templateProjectItem;
        private EnvDTE.DTE dte;
        private Action<string> maCheckOutAction;
        private Action<IEnumerable<string>> projectSyncAction;
        #endregion

        #region Public Methods
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public void OnAfterModelLoaded(ModelTransformExtensionContext context)
        {
            Trace.WriteLine(string.Format("Model was loaded in {0}", context.Project.Name));
            //NOTE: ALF- Couldn't we go through the loaded EDMX file and modify the <Designer><Options><DesignerInfoPropertySet><DesignerProperty Name="CodeGenerationStrategy" value="None">? 
            //Just seems to be an easy thing to forget, also now this is a package we could look at making a button to "enable/disable" the extension so people can still do entity work without
            //this extension playing havoc with their EDMX files? Just something to look in to in the future me thinks.
            
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <history>
        /// modified - churst 3/6/2012 Added logic to only execute when the CodeGenStrategy is set to None
        /// </history>
        public void OnBeforeModelSaved(ModelTransformExtensionContext context)
        {
            string sFullGenCodePath;
            string path;
            string sChildPath;
            List<string> lsAddProjectRefs;
            EnvDTE.ProjectItem pi;
            EnvDTE.ProjectItems piItems;
            bool bIsCodeGenStrategyNone;

            m_entities = new Dictionary<string, XElement>();
            m_associations = new Dictionary<string, XElement>();
            m_mappings = new Dictionary<string, XElement>();
            m_links = new Dictionary<string, string>();

            //Extract a reference to the VisualStudio root/application 
            dte = context.Project.DTE;
            if (dte == null)
            {
                throw new ArgumentNullException("Could not obtain DTE from host");
            }

            //Create an Action delegate that can be used to generically check out files in a generic fashion
            //This allows use to call the delegate passing in any string filename to be checked out.
            maCheckOutAction = sFileName => dte.SourceControl.CheckOutItem(sFileName);
            //projectSyncAction = keepFileNames => ProjectSync(templateProjectItem, keepFileNames);

            //modified - churst 3/5/2012 Add logic to check if the CodeGenerationStrategy is None or not.
            bIsCodeGenStrategyNone = false;
            bIsCodeGenStrategyNone = IsCodeGenerationStrategyDisabled(context.CurrentDocument);

            if (bIsCodeGenStrategyNone == true)
            {

                LoadCSDL(context.CurrentDocument);
                LoadSSDL(context.CurrentDocument);

                if (mlsProjectRefs == null)
                {
                    mlsProjectRefs = new List<string>();
                }

                //Load up the list of existing Project References
                //NOTE: do this each time as users can add project references between runs
                //and since the variable is modular, it doesn't go out of scope.
                LoadProjectReferences(context.Project, mlsProjectRefs);

                //If for some reason the DevXVersion is empty (i.e. someone removed the Default value) then populated with 10.2
                //NOTE: You can only have one <Major>.<Minor> version of DevX installed at a time so don't need to worry about <Build> or <Release> number
                if (msDevXVersion == string.Empty)
                {
                    msDevXVersion = "11.2";
                }

                //Create a list of Project References which need to be included in the project
                lsAddProjectRefs = new List<string>();
                lsAddProjectRefs.Add("System");
                lsAddProjectRefs.Add(string.Format("DevExpress.ExpressApp.v{0}", msDevXVersion));
                lsAddProjectRefs.Add(string.Format("DevExpress.ExpressApp.Validation.v{0}", msDevXVersion));
                lsAddProjectRefs.Add(string.Format("DevExpress.Persistent.Base.v{0}", msDevXVersion));
                lsAddProjectRefs.Add(string.Format("DevExpress.Persistent.BaseImpl.v{0}", msDevXVersion));
                lsAddProjectRefs.Add(string.Format("DevExpress.Xpo.v{0}", msDevXVersion));

                //Add the list of references to the project if they don't already exist
                AddProjectReferences(context.Project, mlsProjectRefs, lsAddProjectRefs);

                path = Path.GetDirectoryName(context.Project.FullName);
                foreach (XElement entity in m_entities.Values)
                {
                    Trace.WriteLine(string.Format("Found {0}", entity.Attribute("Name").Value));

                    if (msGenCodePath != string.Empty)
                    {
                        sFullGenCodePath = string.Format("{0}\\{1}", path, msGenCodePath);
                    }
                    else
                    {
                        sFullGenCodePath = path;
                    }

                    if (Directory.Exists(sFullGenCodePath) == false)
                    {
                        sChildPath = string.Empty;
                        piItems = context.Project.ProjectItems;

                        msGenCodePath.Split('\\').ToList().ForEach(sFolder =>
                            {
                                if (sChildPath.Length > 0)
                                {
                                    sChildPath = string.Format("{0}\\{1}", sChildPath, sFolder);
                                }
                                else
                                {
                                    sChildPath = string.Format("{0}", sFolder);
                                }
                                //if (context.Project.ProjectItems.Item(sChildPath) == null)
                                //{
                                //    context.Project.ProjectItems.AddFolder(string.Format("{0}\\{1}", path, sChildPath));
                                //}

                                //Check to see of the ProjectItem exists in the current level's ProjectItems collection or not
                                pi = FindProjectItem(piItems, sFolder, EnvDTE.Constants.vsProjectItemKindPhysicalFolder);
                                if (pi != null)
                                {
                                    piItems = pi.ProjectItems;
                                }
                                else
                                {
                                    //Check out the ProjectFile if needed in order to add the Folder
                                    CheckoutFileIfRequired(context.Project.FileName);

                                    //if (Directory.Exists(string.Format("{0}\\{1}", path, sChildPath)) == false)
                                    //{
                                    //    Directory.CreateDirectory(string.Format("{0}\\{1}", path, sChildPath));
                                    //}
                                    //pi = piItems.AddFromDirectory(string.Format("{0}\\{1}", path, sChildPath));
                                    //pi = piItems.AddFolder(string.Format("{0}\\{1}", path, sChildPath));
                                    pi = piItems.AddFolder(sFolder);
                                    piItems = pi.ProjectItems;
                                }
                            }
                        );
                        //int iProjItemCount;
                        //bool bFoundIt;

                        //iProjItemCount = pi.ProjectItems.Count;
                        //bFoundIt = false;

                        //for (int iProjItem = 1; iProjItem <= iProjItemCount; iProjItem++)
                        //{
                        //    pi = context.Project.ProjectItems.Item(iProjItemCount);
                        //    if (pi.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder)
                        //    {
                        //        pi.ProjectItems
                        //        if (pi.Name == sFolder)
                        //        {
                        //            bFoundIt = true;
                        //        }
                        //    }
                        //}
                        //context.Project.ProjectItems.AddFromDirectory(string.Format("{0}\\{1}", path, sChildPath));
                        //context.Project.ProjectItems.AddFolder(msGenCodePath);
                        //Directory.CreateDirectory(sFullGenCodePath);
                    }

                    ProcessConcreteClass(entity, sFullGenCodePath, context);
                    ProcessGeneratedClass(entity, sFullGenCodePath, context);
                }
            }
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// This method will determine if the CodeGenerationStrategy is set to None or not.  When
        /// None the GenerationStrategy is assumed to be Disabled and the value returned is True, otherwise False.
        /// </summary>
        /// <param name="axdEDMXDoc"></param>
        /// <returns></returns>
        /// <history>
        /// created - churst 3/6/2012 
        /// </history>
        private bool IsCodeGenerationStrategyDisabled(XDocument axdEDMXDoc)
        {
            bool bIsCodeGenStrateDisabled;
            string sCodeStrateGenName;

            sCodeStrateGenName = DetermineCodeGenerationStrategy(axdEDMXDoc);

            switch (sCodeStrateGenName)
            {
                case "None":
                    bIsCodeGenStrateDisabled = true;
                    break;
                case "Default":
                default:
                    bIsCodeGenStrateDisabled = false;
                    break;
            }

            return (bIsCodeGenStrateDisabled);
        }
        /// <summary>
        /// This Method wil extract out the CodeGenerationStrategy that the user has selected in the EDMX file
        /// </summary>
        /// <param name="axdEDMXDoc"></param>
        /// <returns></returns>
        /// <history>
        /// created - churst 3/6/2012 
        /// </history>
        private string DetermineCodeGenerationStrategy(XDocument axdEDMXDoc)
        {
            XNamespace xnEDMXNS;
            string sCodeGenStrateName;

            sCodeGenStrateName = string.Empty;

            if (axdEDMXDoc != null)
            {
                //http://schemas.microsoft.com/ado/2009/11/edmx
                //sEDMXNamespace = axdEDMXDoc.Root.Name.NamespaceName;
                //xnEDMXNS = "http://schemas.microsoft.com/ado/2008/10/edmx";
                xnEDMXNS = axdEDMXDoc.Root.Name.Namespace;

                //Process the EDMX File to extract the CodeGenerationStrategy that the user has selected
                sCodeGenStrateName = (from XElement xeDesigner in axdEDMXDoc.Root.Elements(XName.Get("Designer", xnEDMXNS.NamespaceName))
                                      from XElement xeOption in xeDesigner.Elements(XName.Get("Options", xnEDMXNS.NamespaceName))
                                      from XElement xeDesignerInfoPropertySet in xeOption.Elements(XName.Get("DesignerInfoPropertySet", xnEDMXNS.NamespaceName))
                                      from XElement xeDesignerProperty in xeDesignerInfoPropertySet.Elements(XName.Get("DesignerProperty", xnEDMXNS.NamespaceName))
                                      where xeDesignerProperty.Attribute("Name").Value == "CodeGenerationStrategy"
                                      select xeDesignerProperty.Attribute("Value").Value).FirstOrDefault();
            }

            return (sCodeGenStrateName);
        }
        /// <summary>
        /// Method used to process the Conceptual Schema Definition Language elements from the design time 
        /// EF Model in order to extract out the entities, relationships and functions that are written to the 
        /// conceptual model at build time.
        /// </summary>
        /// <param name="document"></param>
        private void LoadCSDL(XDocument document)
        {
            XNamespace edmxns = "http://schemas.microsoft.com/ado/2008/10/edmx";
            XNamespace edmns = "http://schemas.microsoft.com/ado/2008/09/edm";
            XNamespace lkhusns = "http://schemas.liekhus.com/entityframework/xaf/customattributes";
            XElement node;
            XElement runtimeNode;
            XElement conceptual;
            XElement csdlContent;
            XElement xeCust;
            XElement container;
            string associationName;
            string entityName;
            string mappingName;

            if (document != null)
            {
                //Check for the Edmx Element in the CSDL Document (i.e. <Edmx>)
                node = document.Element(edmxns + "Edmx");
                if (node != null)
                {
                    //Check for the Runtime Element in the CSDL Document (i.e. <Edmx><Runtime>)
                    runtimeNode = node.Element(edmxns + "Runtime");
                    if (runtimeNode != null)
                    {
                        //Check for the ConceptualModels Element in the CSDL Document (i.e. <Edmx><Runtime><ConceptualModels>)
                        conceptual = runtimeNode.Element(edmxns + "ConceptualModels");
                        if (conceptual != null)
                        {
                            #region Schema Element
                            //Check for the Schema Element in the CSDL Document (i.e. <Edmx><Runtime><ConceptualModels><Schema>)
                            csdlContent = conceptual.Element(edmns + "Schema");
                            if (csdlContent != null)
                            {
                                #region BaseCustomAttribute
                                //Check for our custom BaseCustomAttribute-ProjectGenerationPath Element in the CSDL Document (i.e. <Edmx><Runtime><ConceptualModels><Schema><BaseCustomAttribute-ProjectGenerationPath>)
                                xeCust = csdlContent.Element(lkhusns + "BaseCustomAttribute-ProjectGenerationPath");
                                if (xeCust != null)
                                {
                                    //msGenCodePath = xeCust.Attribute("PropertyValue").Value;
                                    msGenCodePath = xeCust.Value;
                                }

                                //Check for our custom BaseCustomAttribute-DevXVersion Element in the CSDL Document (i.e. <Edmx><Runtime><ConceptualModels><Schema><BaseCustomAttribute-DevXVersion>)
                                xeCust = csdlContent.Element(lkhusns + "BaseCustomAttribute-DevXVersion");
                                if (xeCust != null)
                                {
                                    msDevXVersion = xeCust.Value;
                                }
                                #endregion

                                #region EntityContainer
                                //Check for the EntityContainer Element in the CSDL Document (i.e. <Edmx><Runtime><ConceptualModels><EntityContainer>)
                                container = csdlContent.Element(edmns + "EntityContainer");
                                if (container != null)
                                {
                                    //Loop through all of the EntitySet Elements for the current EntityContainer (i.e. <Edmx><Runtime><ConceptualModels><EntityContainer><EntitySet>)
                                    //and add each EntityType to our Links List of objects for processing
                                    foreach (XElement set in container.Elements(edmns + "EntitySet"))
                                    {
                                        mappingName = set.Attribute("Name").Value;
                                        entityName = set.Attribute("EntityType").Value;

                                        if (!m_links.Keys.Contains(entityName))
                                        {
                                            m_links.Add(new KeyValuePair<string, string>(entityName, mappingName));
                                        }
                                    }
                                }
                                #endregion

                                #region Association
                                //Loop through all of the Association Elements for the current Schema (i.e. <Edmx><Runtime><ConceptualModels><Schema><Association>)
                                foreach (XElement association in csdlContent.Elements(edmns + "Association"))
                                {
                                    associationName = association.Attribute("Name").Value;
                                    if (!string.IsNullOrEmpty(associationName))
                                    {
                                        if (!m_associations.Keys.Contains(associationName))
                                        {
                                            m_associations.Add(new KeyValuePair<string, XElement>(associationName, association));
                                        }
                                    }
                                }
                                #endregion

                                #region EntityType
                                //Loop through all of the EntityType Elements for the current Schema (i.e. <Edmx><Runtime><ConceptualModels><Schema><Association>)
                                foreach (XElement entity in csdlContent.Elements(edmns + "EntityType"))
                                {
                                    entityName = entity.Attribute("Name").Value;
                                    if (!string.IsNullOrEmpty(entityName))
                                    {
                                        if (!m_entities.Keys.Contains(entityName))
                                        {
                                            m_entities.Add(new KeyValuePair<string, XElement>(entityName, entity));
                                        }
                                    }
                                }
                                #endregion
                            }
                            #endregion
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Method used to process the Store Schema Definition Language elements from the design time 
        /// EF Model in order to extract out the store specific commands that are written to the
        /// store model at build time.
        /// </summary>
        /// <param name="document"></param>
        private void LoadSSDL(XDocument document)
        {
            XNamespace edmns = "http://schemas.microsoft.com/ado/2008/09/edm";
            XNamespace edmxns = "http://schemas.microsoft.com/ado/2008/10/edmx";
            XNamespace mappingns = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
            string sModelNamespace;
            XElement xeSchemaNamespace;

            //modified - churst 1/3/2010 Moved the Namespace extract outside the baseClass logic as it's needed below for the NavProp.Relationship/Association logic
            sModelNamespace = string.Empty;
            //Extract the Schema element which contains the Entity Model's Namespace
            xeSchemaNamespace = (from XElement xeSchema in document
                                                        .Elements(edmxns + "Edmx")
                                                        .Elements(edmxns + "Runtime")
                                                        .Elements(edmxns + "ConceptualModels")
                                                        .Elements(edmns + "Schema")
                                 where xeSchema.Attributes().Any(a => a.Name == "Namespace")
                                 select xeSchema).FirstOrDefault();

            if (xeSchemaNamespace != null)
            {
                //The name of the model is in the Namespace Attribute
                sModelNamespace = xeSchemaNamespace.Attribute("Namespace").Value;
            }

            if (document != null)
            {
                XElement node = document.Element(edmxns + "Edmx");
                if (node != null)
                {
                    XElement runtimeNode = node.Element(edmxns + "Runtime");
                    if (runtimeNode != null)
                    {
                        XElement mappings = runtimeNode.Element(edmxns + "Mappings");
                        if (mappings != null)
                        {
                            XElement mapping = mappings.Element(mappingns + "Mapping");

                            if (mapping != null)
                            {
                                XElement containerMapping = mapping.Element(mappingns + "EntityContainerMapping");
                                if (containerMapping != null)
                                {
                                    foreach (XElement setMapping in containerMapping.Elements(mappingns + "EntitySetMapping"))
                                    {
                                        XElement typeMapping = setMapping.Element(mappingns + "EntityTypeMapping");
                                        if (typeMapping != null)
                                        {
                                            string name = string.Empty;
                                            name = typeMapping.Attribute("TypeName").Value;

                                            //modified - churst 1/3/2011 Add code to strip out the Namespace from the Mappings list
                                            if (sModelNamespace != string.Empty)
                                            {
                                                name = name.Replace(string.Format("{0}.", sModelNamespace), string.Empty);
                                            }

                                            if (!m_mappings.Keys.Contains(name))
                                            {
                                                m_mappings.Add(new KeyValuePair<string, XElement>(name, setMapping));
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Method used to generate the Concrete Class for the EF entity (XElement) passed in
        /// to the particular directory path on disk with the given context
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="path"></param>
        /// <param name="context"></param>
        private void ProcessConcreteClass(XElement entity, string path, ModelTransformExtensionContext context)
        {
            string file;
            FileStream stream;
            TextWriter writer;

            file = Path.Combine(path, string.Format("{0}.cs", entity.Attribute("Name").Value));

            //only create the file contents if the file was created this time
            if (!File.Exists(file))
            {
                stream = new FileStream(file, FileMode.CreateNew);
                writer = new StreamWriter(stream);

                writer.WriteLine("//**************************************************************************************");
                writer.WriteLine(string.Format("// {0} Concrete Class File", entity.Attribute("Name").Value));
                writer.WriteLine("//**************************************************************************************");
                writer.WriteLine(string.Format("// Created On: {0} as {1}", DateTime.UtcNow.ToLongDateString(), DateTime.UtcNow.ToLongTimeString()));
                writer.WriteLine("//**************************************************************************************");
                writer.WriteLine(string.Empty);
                writer.WriteLine("using System;");
                writer.WriteLine(string.Empty);
                writer.WriteLine(string.Format("namespace {0}", context.Project.Name));
                writer.WriteLine("{");
                writer.WriteLine(string.Format("{0}public partial class {1}", GenerateTabString(1), entity.Attribute("Name").Value));
                writer.WriteLine(string.Format("{0}{{", GenerateTabString(1)));
                writer.WriteLine(string.Format("{0}}}", GenerateTabString(1)));
                writer.WriteLine("}");

                writer.Close();
                stream.Close();
                
                //The ProjectFile should already be check out, but if not do it now.
                CheckoutFileIfRequired(context.Project.FileName);
                context.Project.ProjectItems.AddFromFile(file);
            }

        }
        /// <summary>
        /// Method use do generate the Generated (partial) Class with the core model content
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="path"></param>
        /// <param name="context"></param>
        /// <history>
        /// modified - churst 8/25/2011 Corrected logic when generating 1:1 relationship properties
        /// </history>
        private void ProcessGeneratedClass(XElement entity, string path, ModelTransformExtensionContext context)
        {
            IList<string> keys;
            IDictionary<string, string> columnMapping;
            bool isLiteObject;
            string file;
            FileStream stream;
            TextWriter writer;
            XElement typeMapping;
            XElement mappingFragment;
            XElement key;
            string propertyName;
            string fieldName;
            bool isPersistentAlias;
            string relationshipName;
            string entityName;
            XElement association;
            string multiplicity;
            string propertyType;
            string fromEnd;
            string toEnd;
            string codeName;
            string sFormattedAttr;
            string baseClass;
            string sInheritedClass;
            string sModelNamespace;
            XElement xeSchemaNamespace;
            bool bOneToOneRelationship;
            string sOtherPropertyType;
            string sPrevFieldName;

            XNamespace edmns = "http://schemas.microsoft.com/ado/2008/09/edm";
            XNamespace mappingns = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
            XNamespace edmxns = "http://schemas.microsoft.com/ado/2008/10/edmx";
            XElement mapping = null;

            keys = new List<string>();
            columnMapping = new Dictionary<string, string>();
            isLiteObject = false;

            //modified - churst 1/3/2010 Moved the Namespace extract outside the baseClass logic as it's needed below for the NavProp.Relationship/Association logic
            sModelNamespace = string.Empty;
            //Extract the Schema element which contains the Entity Model's Namespace
            xeSchemaNamespace = (from XElement xeSchema in context.CurrentDocument
                                                        .Elements(edmxns + "Edmx")
                                                        .Elements(edmxns + "Runtime")
                                                        .Elements(edmxns + "ConceptualModels")
                                                        .Elements(edmns + "Schema")
                                 where xeSchema.Attributes().Any(a => a.Name == "Namespace")
                                 select xeSchema).FirstOrDefault();

            if (xeSchemaNamespace != null)
            {
                //The name of the model is in the Namespace Attribute
                sModelNamespace = xeSchemaNamespace.Attribute("Namespace").Value;
            }

            //modified - churst 1/3/2011 Corrected hardcoded Namespace issue from BusinessModel over to the value in the Namespace property
            if (sModelNamespace != string.Empty)
            {
                if (m_mappings.ContainsKey(string.Format("{0}.{1}", sModelNamespace, entity.Attribute("Name").Value)))
                {
                    mapping = m_mappings[string.Format("{0}.{1}", sModelNamespace, entity.Attribute("Name").Value)];
                }
            }
            else
            {
                if (m_mappings.ContainsKey(string.Format("{0}", entity.Attribute("Name").Value)))
                {
                    mapping = m_mappings[string.Format("{0}", entity.Attribute("Name").Value)];
                }
            }

            file = Path.Combine(path, string.Format("{0}.designer.cs", entity.Attribute("Name").Value));

            if (File.Exists(file))
            {
                //As we are updating and existing Source Code file, make sure it's checked out of SourceControl first
                CheckoutFileIfRequired(file);

                stream = new FileStream(file, FileMode.Truncate);
            }
            else
            {
                stream = new FileStream(file, FileMode.CreateNew);
            }
            writer = new StreamWriter(stream);

            //load column mappings
            if (mapping != null)
            {
                typeMapping = mapping.Element(mappingns + "EntityTypeMapping");
                if (typeMapping != null)
                {
                    mappingFragment = typeMapping.Element(mappingns + "MappingFragment");
                    if (mappingFragment != null)
                    {
                        foreach (XElement scalarMap in mappingFragment.Elements(mappingns + "ScalarProperty"))
                        {
                            if (!columnMapping.ContainsKey(scalarMap.Attribute("Name").Value))
                            {
                                columnMapping.Add(new KeyValuePair<string, string>(scalarMap.Attribute("Name").Value, scalarMap.Attribute("ColumnName").Value));
                            }
                        }
                    }
                }
            }

            //load keys
            key = entity.Element(edmns + "Key");
            if (key != null)
            {
                foreach (XElement reference in key.Elements(edmns + "PropertyRef"))
                {
                    string referenceName = reference.Attribute("Name").Value;

                    if (!keys.Contains(referenceName))
                    {
                        keys.Add(referenceName);
                    }
                }
            }
            isLiteObject = keys.Count > 0;

            writer.WriteLine("//**************************************************************************************");
            writer.WriteLine(string.Format("// {0} Generated Class File", entity.Attribute("Name").Value));
            writer.WriteLine("//**************************************************************************************");
            writer.WriteLine("//**** DO NOT CHANGE THIS FILE.  THE CONTENTS WILL BE OVERRIDEN ON NEXT MODEL SAVE. ****");
            writer.WriteLine("//**************************************************************************************");
            writer.WriteLine("using System;");
            writer.WriteLine("using System.ComponentModel;");
            writer.WriteLine("using DevExpress.ExpressApp;");
            writer.WriteLine("using DevExpress.ExpressApp.Validation;");
            writer.WriteLine("using DevExpress.Persistent.Base;");
            writer.WriteLine("using DevExpress.Persistent.BaseImpl;");
            writer.WriteLine("using DevExpress.Persistent.Validation;");
            writer.WriteLine("using DevExpress.Xpo;");
            writer.WriteLine(string.Empty);
            writer.WriteLine(string.Format("namespace {0}", context.Project.Name));
            writer.WriteLine("{");

            foreach (XElement element in entity.Elements())
            {
                if (element.Name.LocalName.Contains("BaseCustomAttribute"))
                {
                    writer.WriteLine(string.Format("{0}{1}", GenerateTabString(1), GetFormattedAttribute(element, entity, null)));
                }
            }

            //modified - churst 1/3/2011 Corrected the hardcoded Namespace over to use the extracted Property value
            if (sModelNamespace != string.Empty)
            {
                if (m_mappings.ContainsKey(string.Format("{0}.{1}", sModelNamespace, entity.Attribute("Name").Value)))
                {
                    writer.WriteLine(string.Format("{0}[Persistent(\"{1}\")]", GenerateTabString(1), mapping.Attribute("Name").Value));
                }
            }
            else
            {
                if (m_mappings.ContainsKey(entity.Attribute("Name").Value))
                {
                    writer.WriteLine(string.Format("{0}[Persistent(\"{1}\")]", GenerateTabString(1), mapping.Attribute("Name").Value));
                }
            }

            //Generate Class
            baseClass = string.Empty;
            if (entity.Attributes().Any(a => a.Name == "BaseType") == true)
            {
                baseClass = entity.Attribute("BaseType").Value;
            }

            //Determine the class inheritance to be used
            if (baseClass != string.Empty)
            {
                sInheritedClass = baseClass.Replace(string.Format("{0}.", sModelNamespace), string.Empty);
            }
            else if (isLiteObject == true)
            {
                sInheritedClass = "XPLiteObject";
            }
            else
            {
                sInheritedClass = "BaseObject";
            }

            //TODO: Think about using fully qualified namespace with class name
            writer.WriteLine(string.Format("{0}public partial class {1} : {2}", GenerateTabString(1), entity.Attribute("Name").Value, sInheritedClass));
            writer.WriteLine(string.Format("{0}{{", GenerateTabString(1)));

            #region Constructor Gen
            //Generate Constructors
            writer.WriteLine(string.Format("{0}#region Constructor", GenerateTabString(2)));
            writer.WriteLine(string.Format("{0}public {1}() : base()", GenerateTabString(2), entity.Attribute("Name").Value));
            writer.WriteLine(string.Format("{0}{{", GenerateTabString(2)));
            writer.WriteLine(string.Format("{0}}}", GenerateTabString(2)));
            writer.WriteLine(string.Empty);

            writer.WriteLine(string.Format("{0}public {1}(Session session) : base(session)", GenerateTabString(2), entity.Attribute("Name").Value));
            writer.WriteLine(string.Format("{0}{{", GenerateTabString(2)));
            writer.WriteLine(string.Format("{0}}}", GenerateTabString(2)));
            writer.WriteLine(string.Format("{0}#endregion", GenerateTabString(2)));
            writer.WriteLine(string.Empty);
            #endregion

            writer.WriteLine(string.Format("{0}#region Properties", GenerateTabString(2)));

            //Generate Standard Properties
            foreach (XElement property in entity.Elements(edmns + "Property"))
            {
                #region Modular Variable Gen
                //Modular Level class variable declaration
                propertyName = property.Attribute("Name").Value;
                fieldName = string.Format("m_{0}{1}", propertyName.Substring(0, 1).ToLower(), propertyName.Substring(1));
                writer.WriteLine(string.Format("{0}private {1} {2};", GenerateTabString(2), property.Attribute("Type").Value, fieldName));
                #endregion

                #region Property Custom Attribute Gen
                //Generate any custom attributes the entity/property is tagged with in the model
                //This includes Size and PersistentAlias
                isPersistentAlias = false;
                foreach (XElement element in property.Elements().Where(t => t.Name.LocalName.Contains("BaseCustomAttribute")))
                {
                    //Check that the formatted attribute actually contains data before generating it
                    sFormattedAttr = GetFormattedAttribute(element, entity, property);
                    if (sFormattedAttr != string.Empty)
                    {
                        writer.WriteLine(string.Format("{0}{1}", GenerateTabString(2), sFormattedAttr));
                    }

                    //Determine if this Element is only a persisted Alias or not as special processing may be needed
                    if (element.Name.LocalName.Contains("PersistentAlias"))
                    {
                        isPersistentAlias = true;
                    }
                }
                #endregion

                #region Property Gen
                //column mapping for persistence
                if (columnMapping.ContainsKey(propertyName) &&
                    !isPersistentAlias)
                {
                    writer.WriteLine(string.Format("{0}[Persistent(\"{1}\")]", GenerateTabString(2), columnMapping[propertyName]));
                }

                //key field attribute
                if (keys.Contains(propertyName))
                {
                    writer.WriteLine(string.Format("{0}[Key(true)]", GenerateTabString(2)));
                }

                //Generate Property
                writer.WriteLine(string.Format("{0}public {1} {2}", GenerateTabString(2), property.Attribute("Type").Value, propertyName));
                writer.WriteLine(string.Format("{0}{{", GenerateTabString(2)));

                if (!isPersistentAlias)
                {
                    writer.WriteLine(string.Format("{0}get {{ return {1}; }}", GenerateTabString(3), fieldName));
                    writer.WriteLine(string.Format("{0}set {{ SetPropertyValue(\"{1}\", ref {2}, value); }}", GenerateTabString(3), propertyName, fieldName));
                }
                else
                {
                    writer.WriteLine(string.Format("{0}get {{ return EvaluateAlias(\"{1}\") as {2}; }}", GenerateTabString(3), propertyName, property.Attribute("Type").Value));
                }
                writer.WriteLine(string.Format("{0}}}", GenerateTabString(2)));
                writer.WriteLine(string.Empty);
                #endregion
            }

            //Generate Relationship/Collection Properties
            foreach (XElement relationship in entity.Elements(edmns + "NavigationProperty"))
            {
                bOneToOneRelationship = false;
                sOtherPropertyType = "";

                relationshipName = relationship.Attribute("Relationship").Value;
                //modified - churst 1/3/2011 Stripping out the Namespace portion of the Relationship name
                if (sModelNamespace != string.Empty)
                {
                    relationshipName = relationshipName.Replace(string.Format("{0}.", sModelNamespace), string.Empty);
                }
                entityName = relationshipName;
                association = null;

                if (m_associations.ContainsKey(entityName))
                {
                    association = m_associations[entityName];
                }

                #region Determine Multiplicity
                //Determine the relationship type for this entity
                multiplicity = "*";
                propertyName = propertyName = relationship.Attribute("Name").Value;
                propertyType = string.Empty;

                if (association != null)
                {
                    if (association.Elements(edmns + "End").Attributes("Multiplicity").Where(m => m.Value == "1").Count() > 1)
                    {
                        bOneToOneRelationship = true;
                    }

                    foreach (XElement end in association.Elements(edmns + "End"))
                    {
                        //Find if this End element's Role matches the ToRole we are currently processing or not
                        if (end.Attribute("Role").Value.Equals(relationship.Attribute("ToRole").Value))
                        {
                            multiplicity = end.Attribute("Multiplicity").Value;
                            if (sModelNamespace != string.Empty)
                            {
                                propertyType = end.Attribute("Type").Value.Replace(string.Format("{0}.", sModelNamespace), string.Empty);
                            }
                            else
                            {
                                propertyType = end.Attribute("Type").Value;
                            }
                        }
                        else 
                        { 
                            //As we are processing the FromRole side of the association, if this happens to be a 1:1 Relationship
                            //then extract the value into the OtherPropertyType
                            if (bOneToOneRelationship == true)
                            {
                                if (sModelNamespace != string.Empty)
                                {
                                    sOtherPropertyType = end.Attribute("Type").Value.Replace(string.Format("{0}.", sModelNamespace), string.Empty);
                                }
                                else
                                {
                                    sOtherPropertyType = end.Attribute("Type").Value;
                                }
                            }
                        }
                    }
                }
                else
                {
                    fromEnd = relationship.Attribute("FromRole").Value;
                    toEnd = relationship.Attribute("ToRole").Value;
                    codeName = relationship.Attribute("Relationship").Value;

                    propertyType = toEnd;
                    int fromPosition = codeName.IndexOf(fromEnd);
                    int toPosition = codeName.IndexOf(toEnd, fromPosition + 1);
                    if (fromPosition < toPosition)
                    {
                        multiplicity = "*";
                    }
                    else
                    {
                        multiplicity = "1";
                    }
                }
                #endregion

                //Determine if the relationship is a 1 to Many or a 1 to 1
                if (multiplicity.Equals("*"))
                {
                    writer.WriteLine(string.Format("{0}[Association(\"{1}\")]", GenerateTabString(2), relationshipName));

                    #region Property Custom Attribute Gen
                    //Generate any custom attributes the entity/property is tagged with in the model
                    foreach (XElement element in relationship.Elements())
                    {
                        if (element.Name.LocalName.Contains("BaseCustomAttribute"))
                        {
                            //Check that the formatted attribute actually contains data before generating it
                            sFormattedAttr = GetFormattedAttribute(element, entity, relationship);
                            if (sFormattedAttr != string.Empty)
                            {
                                writer.WriteLine(string.Format("{0}{1}", GenerateTabString(2), sFormattedAttr));
                            }
                        }
                    } 
                    
                    if (association != null)
                    {
                        foreach (XElement element in association.Elements())
                        {
                            if (element.Name.LocalName.Contains("BaseCustomAttribute"))
                            {
                                writer.WriteLine(string.Format("{0}{1}", GenerateTabString(2), GetFormattedAttribute(element, entity, null)));
                            }
                        }
                    }
                    #endregion

                    #region Property Gen
                    writer.WriteLine(string.Format("{0}public XPCollection<{1}> {2}", GenerateTabString(2), propertyType, propertyName));
                    writer.WriteLine(string.Format("{0}{{", GenerateTabString(2)));
                    writer.WriteLine(string.Format("{0}get", GenerateTabString(3)));
                    writer.WriteLine(string.Format("{0}{{", GenerateTabString(3)));
                    writer.WriteLine(string.Format("{0}return GetCollection<{1}>(\"{2}\");", GenerateTabString(4), propertyType, propertyName));
                    writer.WriteLine(string.Format("{0}}}", GenerateTabString(3)));
                    writer.WriteLine(string.Format("{0}}}", GenerateTabString(2)));
                    #endregion
                }
                else
                {
                    #region Modular Variable Gen
                    //Modular Level class variable declaration
                    fieldName = string.Format("m_{0}{1}", propertyName.Substring(0, 1).ToLower(), propertyName.Substring(1));
                    writer.WriteLine(string.Format("{0}private {1} {2};", GenerateTabString(2), propertyType, fieldName));
                    #endregion

                    foreach (XElement element in relationship.Elements())
                    {
                        if (element.Name.LocalName.Contains("BaseCustomAttribute"))
                        {
                            //Check that the formatted attribute actually contains data before generating it
                            sFormattedAttr = GetFormattedAttribute(element, entity, relationship);
                            if (sFormattedAttr != string.Empty)
                            {
                                writer.WriteLine(string.Format("{0}{1}", GenerateTabString(2), sFormattedAttr));
                            }
                        }
                    }

                    if (association != null)
                    {
                        foreach (XElement element in association.Elements())
                        {
                            if (element.Name.LocalName.Contains("BaseCustomAttribute"))
                            {
                                writer.WriteLine(string.Format("{0}{1}", GenerateTabString(2), GetFormattedAttribute(element, entity, null)));
                            }
                        }
                    }

                    //Determine if we are processing a one to one relationship or not as this requires special processing
                    if (bOneToOneRelationship == false)
                    {
                        writer.WriteLine(string.Format("{0}[Association(\"{1}\")]", GenerateTabString(2), relationshipName));

                        #region Property Gen
                        writer.WriteLine(string.Format("{0}public {1} {2}", GenerateTabString(2), propertyType, propertyName));
                        writer.WriteLine(string.Format("{0}{{", GenerateTabString(2)));
                        writer.WriteLine(string.Format("{0}get {{ return {1}; }}", GenerateTabString(3), fieldName));
                        writer.WriteLine(string.Format("{0}set {{ SetPropertyValue(\"{1}\", ref {2}, value); }}", GenerateTabString(3), propertyName, fieldName));
                        writer.WriteLine(string.Format("{0}}}", GenerateTabString(2)));
                        #endregion
                    }
                    else
                    {
                        //Local "Previous" field declaration
                        sPrevFieldName = string.Format("prev{0}", propertyType);

                        #region Property Gen
                        writer.WriteLine(string.Format("{0}public {1} {2}", GenerateTabString(2), propertyType, propertyName));
                        writer.WriteLine(string.Format("{0}{{", GenerateTabString(2)));
                        writer.WriteLine(string.Format("{0}get {{ return {1}; }}", GenerateTabString(3), fieldName));
                        writer.WriteLine(string.Format("{0}set", GenerateTabString(3)));
                        writer.WriteLine(string.Format("{0}{{", GenerateTabString(3)));
                        writer.WriteLine(string.Format("{0}if({1} == value)", GenerateTabString(4), fieldName));
                        writer.WriteLine(string.Format("{0}{{", GenerateTabString(4)));
                        writer.WriteLine(string.Format("{0}return;", GenerateTabString(5)));
                        writer.WriteLine(string.Format("{0}}}", GenerateTabString(4)));
                        writer.WriteLine("");
                        writer.WriteLine(string.Format("{0}{1} {2};", GenerateTabString(4), propertyType, sPrevFieldName));
                        writer.WriteLine(string.Format("{0}{1} = {2};", GenerateTabString(4), sPrevFieldName, fieldName));
                        writer.WriteLine(string.Format("{0}{1} = value;", GenerateTabString(4), fieldName));
                        writer.WriteLine("");
                        writer.WriteLine(string.Format("{0}if(this.IsLoading == true)", GenerateTabString(4)));
                        writer.WriteLine(string.Format("{0}{{", GenerateTabString(4)));
                        writer.WriteLine(string.Format("{0}return;", GenerateTabString(5)));
                        writer.WriteLine(string.Format("{0}}}", GenerateTabString(4)));
                        writer.WriteLine("");
                        writer.WriteLine(string.Format("{0}if(({1} != null) &&", GenerateTabString(4), sPrevFieldName));
                        writer.WriteLine(string.Format("{0}  ({1}.{2} == this))", GenerateTabString(4), sPrevFieldName, sOtherPropertyType));
                        writer.WriteLine(string.Format("{0}{{", GenerateTabString(4)));
                        writer.WriteLine(string.Format("{0}{1}.{2} = null;", GenerateTabString(5), sPrevFieldName, sOtherPropertyType));
                        writer.WriteLine(string.Format("{0}}}", GenerateTabString(4)));
                        writer.WriteLine("");
                        writer.WriteLine(string.Format("{0}if({1} != null)", GenerateTabString(4), fieldName));
                        writer.WriteLine(string.Format("{0}{{", GenerateTabString(4)));
                        writer.WriteLine(string.Format("{0}{1}.{2} = this;", GenerateTabString(5), fieldName, sOtherPropertyType));
                        writer.WriteLine(string.Format("{0}}}", GenerateTabString(4)));
                        writer.WriteLine("");
                        writer.WriteLine(string.Format("{0}OnChanged(\"{1}\");", GenerateTabString(4), propertyName));
                        writer.WriteLine(string.Format("{0}}}", GenerateTabString(3)));
                        writer.WriteLine(string.Format("{0}}}", GenerateTabString(2)));
                        #endregion
                    }
                }

                writer.WriteLine(string.Empty);
            }

            writer.WriteLine(string.Format("{0}#endregion", GenerateTabString(2)));
            writer.WriteLine(string.Format("{0}}}", GenerateTabString(1)));
            writer.WriteLine("}");

            writer.Close();
            stream.Close();
            
            //The ProjectFile should already be check out, but if not do it now.
            CheckoutFileIfRequired(context.Project.FileName);
            context.Project.ProjectItems.AddFromFile(file);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="customAttribute"></param>
        /// <param name="entity"></param>
        /// <param name="property"></param>
        /// <returns></returns>
        private string GetFormattedAttribute(XElement customAttribute, XElement entity, XElement property)
        {
            XNamespace customns;
            string format;
            string sValue;
            bool bValOutput;

            customns = "http://schemas.liekhus.com/entityframework/xaf/customattributes";

            format = customAttribute.Attribute(customns + "PropertyFormat").Value;
            format = format.Replace("&quot;", "\"");

            sValue = customAttribute.Value;

            //Check if the custom property be processed is of the kind which does not use the value set
            //rather uses the value to indicate that the custom property should be included as is (bit flag)
            if ((format.Contains("{value}") == false) &&
                (bool.TryParse(sValue, out bValOutput) == true))
            {
                //If the value is false then clear the formatted result as it shouldn't be generated
                //in the resulting business object
                if (bValOutput == false)
                {
                    format = string.Empty;
                }
            }
            else
            {
                format = format.Replace("{value}", customAttribute.Value);
                format = format.Replace("{entity}", entity.Attribute("Name").Value);
            }

            if (property != null)
            {
                format = format.Replace("{property}", property.Attribute("Name").Value);
            }

            return format;
        }
        /// <summary>
        /// Method used to generate a string of four spaces for each aiCount 'Tab' characters generated 
        /// as done by the VS IDE
        /// </summary>
        /// <param name="aiCount"></param>
        /// <returns></returns>
        private static string GenerateTabString(int aiCount)
        {
            string sResult = string.Empty;

            return (sResult.PadRight(4 * aiCount));
        }
        /// <summary>
        /// Method will load up the list of Project References for the provided project.
        /// This list will be used later to determine if a Project reference already exists or should be added.
        /// NOTE: this list is *not* Fully Qualified so difference versions with the same name may cause problems in the future
        /// </summary>
        /// <param name="apProject"></param>
        /// <param name="alsProjectRefs"></param>
        private void LoadProjectReferences(EnvDTE.Project apProject, List<string> alsProjectRefs)
        {
            VSLangProj.VSProject vspVSProject;
            VSLangProj.References irReferences;
            VSLangProj80.Reference3 irReference3;

            if (alsProjectRefs == null)
            {
                alsProjectRefs = new List<string>();
            }

            irReferences = null;
            vspVSProject = apProject.Object as VSLangProj.VSProject;
            if (vspVSProject != null)
            {
                irReferences = vspVSProject.References;
            }

            foreach (VSLangProj80.Reference3 irRef3 in irReferences)
            {
                irReference3 = irRef3;
                //Added logic in the verify the reference isn't already in the list
                if (alsProjectRefs.Contains(irReference3.Identity) == false)
                {
                    alsProjectRefs.Add(irReference3.Identity);
                }
            }
        }
        /// <summary>
        /// Method will process all the [Add] Project Reference names which need to be included
        /// in the project specified.
        /// </summary>
        /// <param name="apProject"></param>
        /// <param name="alsProjectRefs"></param>
        /// <param name="alsAddProjectRefs"></param>
        private void AddProjectReferences(EnvDTE.Project apProject, List<string> alsProjectRefs, List<string> alsAddProjectRefs)
        {
            VSLangProj.VSProject vspVSProject;
            bool bResult;
            string sAddRefError;
            List<string> lsAddRefErrors;

            try
            {
                //Check the Project file out of SourceControl if needed
                CheckoutFileIfRequired(apProject.FileName);

                lsAddRefErrors = new List<string>();

                //Loop through all of the Project References which need to be added
                //and process them if they don't already exist
                foreach (string sReferenceName in alsAddProjectRefs)
                {
                    sAddRefError = string.Empty;

                    if (alsProjectRefs.Contains(sReferenceName) == false)
                    {
                        vspVSProject = apProject.Object as VSLangProj.VSProject;
                        if (vspVSProject != null)
                        {
                            //Add the reference to the Project
                            bResult = AddProjectReference(vspVSProject, sReferenceName, out sAddRefError);

                            if (bResult == true)
                            {
                                alsProjectRefs.Add(sReferenceName);
                            }
                            else
                            {
                                //TODO: currently not doing anything with the errors if they occur.
                                lsAddRefErrors.Add(sAddRefError);
                            }
                        }
                    }
                }
            }
            catch (System.Runtime.InteropServices.COMException ceException)
            {
                System.Diagnostics.Debug.WriteLine(ceException.Message);
            }

        }
        /// <summary>
        /// Method is a wrapper around adding a Project Reference so if it fails the remaining references
        /// can be processed and added into the project
        /// </summary>
        /// <param name="avspProject"></param>
        /// <param name="asReferenceName"></param>
        /// <param name="asAddRefError"></param>
        /// <returns></returns>
        private bool AddProjectReference(VSLangProj.VSProject avspProject, string asReferenceName, out string asAddRefError)
        {
            bool bResult = false;

            try
            {
                //Initialize the Add Ref error message
                asAddRefError = string.Empty;

                //Add the project reference
                avspProject.References.Add(asReferenceName);
                bResult = true;
            }
            catch (COMException ceException)
            {
                asAddRefError = ceException.Message;
                System.Diagnostics.Debug.WriteLine(ceException.Message);
            }
            return (bResult);
        }
        /// <summary>
        /// This method will loop through the list of ProjectItems and locate any matching ProjectItem with the name in asName and of ProjectItem.Kind 
        /// in the asEnvDTEConstants
        /// </summary>
        /// <param name="apiItems"></param>
        /// <param name="asName"></param>
        /// <param name="asEnvDTEConstants">One of the EnvDTE.Constants.* values</param>
        /// <returns></returns>
        private EnvDTE.ProjectItem FindProjectItem(EnvDTE.ProjectItems apiItems, string asName, string asEnvDTEConstants)
        {
            EnvDTE.ProjectItem pi = null;
            EnvDTE.ProjectItem piResult = null;
            int iProjItemCount;

            if ((apiItems != null) &&
                (apiItems.Count > 0))
            {
                iProjItemCount = apiItems.Count;

                for (int iProjItem = 1; iProjItem <= iProjItemCount; iProjItem++)
                {
                    pi = apiItems.Item(iProjItemCount);

                    //if (pi.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder)
                    if ((pi.Name == asName) &&
                        (pi.Kind == asEnvDTEConstants))
                    {
                        piResult = pi;
                        break;
                    }
                }
            }

            return (piResult);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="templateProjectItem"></param>
        /// <param name="keepFileNames"></param>
        private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, IEnumerable<string> keepFileNames)
        {
            var keepFileNameSet = new HashSet<string>(keepFileNames);
            var projectFiles = new Dictionary<string, EnvDTE.ProjectItem>();
            var originalOutput = Path.GetFileNameWithoutExtension(templateProjectItem.FileNames[0]);

            foreach (EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems)
            {
                projectFiles.Add(projectItem.FileNames[0], projectItem);
            }

            // Remove unused items from the project
            foreach (var pair in projectFiles)
            {
                if (keepFileNames.Contains(pair.Key) == false && 
                    (Path.GetFileNameWithoutExtension(pair.Key) + ".").StartsWith(originalOutput + ".") == false)
                {
                    pair.Value.Delete();
                }
            }

            // Add missing files to the project
            foreach (string fileName in keepFileNameSet)
            {
                if (!projectFiles.ContainsKey(fileName))
                {
                    templateProjectItem.ProjectItems.AddFromFile(fileName);
                }
            }
        }

        /// <summary>
        /// This will check if a the file in question is under Source Code Control and not already checked out
        /// at which point it will check it out for you.
        /// </summary>
        /// <param name="asFileName">The full path of the file to be check out of Source Code Control</param>
        private void CheckoutFileIfRequired(string asFileName)
        {
            if (dte.SourceControl == null || 
                dte.SourceControl.IsItemUnderSCC(asFileName) == false || 
                dte.SourceControl.IsItemCheckedOut(asFileName) == true)
            {
                return;
            }

            //run on worker thread to prevent T4 calling back into VS
            maCheckOutAction.EndInvoke(maCheckOutAction.BeginInvoke(asFileName, null, null));
        }
        #endregion
    }
}
